/***************************************************************************
 *   Copyright (C) 2007 by Warren Kenny   				   *
 *   warren.kenny@gmail.com   						   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#ifndef GSARRAY_H
#define GSARRAY_H

#include <gsystems/gsCore.h>
#include <gsystems/gsContainer.h>

template<class T>
class gsArray : public gsContainer<T>
{
	
public:
	
	/**
	 *	Default constructor
	 */
	inline gsArray() :
	m_size( 0 ),
	m_capacity( 0 ),
	m_objects( GS_NULL )
	{}
	
	/**
	 * 	Constructor which specifies the initial capacity of the array 
	 * @param capacity The initial capacity of the array
	 */
	inline gsArray( const gsUInt capacity ) :
	m_size( 0 ),
	m_capacity( capacity ),
	m_objects( GS_NULL )
	{
		if( m_capacity > 0 )
		{
			m_objects = new T[ m_capacity ];
		}
	}
	
	/**
	 * 	Constructor which specifies the initial capacity of the array 
	 * @param capacity The initial capacity of the array
	 */
	inline gsArray( const gsContainer<T>& container ) :
	m_size( container.m_size ),
	m_capacity( container.m_capacity )
	{
		if( m_capacity > 0 )
		{
			m_objects = new T[ m_capacity ];
			for( gsUInt i = 0; i < m_size ; i++ )
			{
				m_objects[i] = container.m_objects[i];
			}
		}
	}
	
	/**
	 * 	Generic method for inserting an object into the container, overridden from gsContainer
	 * @param object The object to be inserted
	 * @return 	- True is the object was successfully inserted
	 *		- False otherwise
	 */
	inline virtual gsBool insert( const T& object )
	{
		expand();
		m_objects[m_size++] = object;
		return true;
	}
		
	/**
	 * 	Generic method for removing an object from the container, overridden from gsContainer
	 * @param object The object to be removed
	 * @return 	- True if the object was found and removed successfully
	 *		- False otherwise
	 */
	inline virtual gsBool remove( const T& object )
	{
		
		return true;
	}
		
	/**
	 * 	Generic function for indicating whether a given object exists within the container, overridden from gsContainer
	 * @param object The object to be found
	 * @return 	- True if the object was found
	 *		- False otherwise
	 */
	inline virtual gsBool find( const T& object )
	{
		gsBool found = false;
		for( gsUInt i = 0 ; i < m_size ; i++ )
		{
			if( m_objects[i] == object )
			{
				found = true;
				break;
			}
		}
		return found;
	}
	
	/**
	 *        Generic function for retrieving an object from the specified index within the container, overridden from gsContainer
	 * @param index The index within the container from which the object should be retrieved
	 * @param object A reference to the object to be set
	 * @return 	- True if the object was retrieved
	 *		- False otherwise
	 */
	inline virtual gsBool get( const gsUInt index, T& object )
	{
		gsBool ok = ( index < m_size );
		if( ok )
		{
			object = m_objects[ index ];
		}
		return ok;
	}
	
	/**
	 *	Generic function for indicating whether the container is empty
	 * @return 	- True if the container is empty
	 *		- False if the container holds one or more objects
	 */
	inline virtual gsBool empty()
	{
		return ( m_size == 0 );
	}	

	/**
	 *	Generic function for finding the number of elements in the container, overridden from gsContainer
	 * @return	The number of elements in the container 
	 */
	inline virtual const gsUInt getSize()
	{
		return m_size;
	}
	
	/**
	 * 	Sets the capacity to the value specified by newCapacity; if newCapacity is smaller than the current capacity of the
	 * 	array, some contained objects may be lost
	 * @param newCapacity The new capacity of the array
	 */
	inline void setCapacity( const gsUInt newCapacity )
	{
		resize( newCapacity );
	}
	
	/**
	 * 	Overloaded operator; allows for retrieval of objects from the specified array index
	 * @param arrayIndex The index where the object should be retrieved from
	 * @return The object
	 */
	inline T& operator[]( const gsUInt arrayIndex )
	{
		assert( arrayIndex < m_size );
		return m_objects[ arrayIndex ];
	}
	
	/**
	 * 	Overloaded operator; allows for const access to objects 
	 * @param arrayIndex The index where the object should be retrieved from
	 * @return The object
	 */
	inline const T& operator[]( const gsUInt arrayIndex ) const
	{
		assert( arrayIndex < m_size );
		return m_objects[ arrayIndex ];
	}
	
	/**
	 *        Overloaded operator; allows the contents of another gsContainer to be copied into this gsArray
	 * @param array The other container
	 * @return A reference to the new gsArray
	 */
	inline T& operator=( gsContainer<T> &container )
	{
		if( this != &container )
		{
		
			if( container.getSize() > m_capacity )
			{
				resizeNoPreserve( container.getSize() );
			}
		
			gsUInt counter = 0;
			for( gsUInt i = 0 ; i < container.getSize() ; i++ )
			{
				T object;
				if( container.get( i, object ) )
				{
					m_objects[counter] = object;
					counter++;
				}
			}
			m_size = counter;
	
		}
		return (*this);
	}
	
	/**
	 * 	Destructs all objects in the array and deletes the array's storage buffer
	 */
	inline void clear()
	{
		if( m_objects )
		{
			for( gsUInt i = 0; i < m_size; i++ )
			{
				m_objects[i].~T();
			}
			delete[] m_objects;
			m_objects = GS_NULL;
			m_size = m_capacity = 0;
		}
	}
	
	/**
	 * 	Default destructor
	 */
	~gsArray()
	{
		clear();
	}

protected:
	
	inline void expand()
	{
		if( m_size == m_capacity )
		{
			resize( m_capacity + 1 );
		}
	}
	
	/**
	 * 	Resizes the array to the specified new size, copying old objects into the new buffer
	 * @param newSize 
	 */
	inline void resize( gsUInt newSize )
	{
		if( newSize > 0 && newSize != m_capacity )
		{
			T* temp = new T[ newSize ];
			gsUInt size;
			
			if( newSize > m_capacity )
			{
				size = m_capacity;
			}
			else
			{
				size = newSize;
			}
			
			for( gsUInt i = 0 ; i < size ; i++ )
			{
				temp[i] = m_objects[i];
			}
	
			if( m_objects )
			{
				delete[] m_objects;
			}
	
			m_objects = temp;
			m_capacity = newSize;
		}
	}
		
	/**
	 * 	Resizes the array to the specified new size without preserving old objects
	 * @param newSize 
	 */
	inline void resizeNoPreserve( gsUInt newSize )
	{
		if( newSize > 0 && newSize != m_capacity )
		{
			T* temp = new T[ newSize ];
		
			if( m_objects )
			{
				delete[] m_objects;
			}
		
			m_objects = temp;
			m_capacity = newSize;
		}
	}
	
private:
		
	// Pointer to the object buffer
	T* m_objects;
	
	// The number of objects in the container
	gsUInt m_size;
	
	// The allocated capacity of this container
	gsUInt m_capacity;
};

#endif
